/**********************************************************************
* Description:
* Created By:	
* Date Created:	
*
* $Workfile: CheckInBLL.cs $
* $Revision: 40 $ 
* $Header: /trunk/Arena.Custom.Cccev/Arena.Custom.Cccev.CheckIn/CheckInBLL.cs   40   2009-06-18 15:45:33-07:00   nicka $
* 
* $Log: /trunk/Arena.Custom.Cccev/Arena.Custom.Cccev.CheckIn/CheckInBLL.cs $
*  
*  Revision: 40   Date: 2009-06-18 22:45:33Z   User: nicka 
*  DanielH|HDC patch 
*  
*  Revision: 39   Date: 2009-06-18 17:43:42Z   User: nicka 
*  Changes to handle new IPrintLabel that requires kiosk as discussed here: 
*  http://checkinwizard.codeplex.com/Thread/View.aspx?ThreadId=57675 
*  
*  Revision: 38   Date: 2009-06-08 18:39:10Z   User: JasonO 
*  Implementing reSharper recommendations. 
*  
*  Revision: 37   Date: 2009-06-05 00:12:07Z   User: JasonO 
*  Adding new GetCurrentKiosk() method to return instance of kiosk.  Decouples 
*  DNS/IP lookup logic from UI. 
*  
*  Revision: 36   Date: 2009-05-18 17:46:39Z   User: JasonO 
*  Added publid Print() method to provide API hooks to re-print labels 
*  manually. 
*  
*  Revision: 35   Date: 2009-05-18 17:25:57Z   User: JasonO 
*  Cleaning up unused constants. 
*  
*  Revision: 34   Date: 2009-05-18 16:58:17Z   User: JasonO 
*  Cleaning up usings. 
*  
*  Revision: 33   Date: 2009-05-18 16:51:24Z   User: JasonO 
*  Updating to call ArenaContext.Current.Organization.GradePromotionDate over 
*  explicit calls to specific GradePromotionDate org setting.  The property on 
*  the Organization class provides a default value of 6/1 instead of throwing 
*  an exception. 
*  
*  Revision: 32   Date: 2009-05-06 16:57:49Z   User: JasonO 
*  Adding tag sync/matching. 
*  
*  Revision: 31   Date: 2009-02-17 01:08:31Z   User: nicka 
*  simplify the AbilityLevel check... the pa IntValue is the AbilityLevel LUID 
*  
*  Revision: 30   Date: 2009-02-17 00:52:54Z   User: nicka 
*  Changed OccurrenceTypeAttribute AbilityLevel property from column to 
*  skel_bone (multi value) 
*  
*  Revision: 29   Date: 2009-02-06 18:46:29Z   User: JasonO 
*  Updating results table to fix CSS issue. 
*  
*  Revision: 28   Date: 2009-02-04 18:24:55Z   User: JasonO 
*  
*  Revision: 27   Date: 2009-02-03 23:23:58Z   User: JasonO 
*  Fixing bug that was preventing location name from appearing on result view. 
*  
*  Revision: 26   Date: 2009-01-29 17:14:09Z   User: JasonO 
*  
*  Revision: 25   Date: 2009-01-28 17:48:15Z   User: JasonO 
*  
*  Revision: 24   Date: 2009-01-28 00:06:36Z   User: JasonO 
*  squishing bugs and tweeking css 
*  
*  Revision: 23   Date: 2009-01-27 23:19:40Z   User: JasonO 
*  
*  Revision: 22   Date: 2009-01-27 20:51:08Z   User: JasonO 
*  
*  Revision: 21   Date: 2009-01-27 15:54:21Z   User: JasonO 
*  
*  Revision: 20   Date: 2009-01-27 15:51:01Z   User: JasonO 
*  
*  Revision: 19   Date: 2009-01-22 16:35:12Z   User: JasonO 
*  adding final touches 
*  
*  Revision: 18   Date: 2009-01-19 08:08:17Z   User: nicka 
*  Fix 1/19 NA: Changed to work without requiring an OccurrenceTypeAttribute 
*  for each AttendanceType 
*  
*  Revision: 17   Date: 2009-01-18 00:50:47Z   User: JasonO 
*  fixing bugs 
*  
*  Revision: 16   Date: 2009-01-16 18:32:10Z   User: JasonO 
*  
*  Revision: 15   Date: 2009-01-15 22:00:57Z   User: JasonO 
*  
*  Revision: 14   Date: 2009-01-06 22:14:13Z   User: JasonO 
*  Sprint completion! 
*  
*  Revision: 13   Date: 2009-01-06 18:09:13Z   User: nicka 
*  
*  Revision: 12   Date: 2009-01-06 05:15:23Z   User: nicka 
*  set label values 
*  
*  Revision: 11   Date: 2009-01-06 00:08:52Z   User: JasonO 
*  
*  Revision: 10   Date: 2009-01-06 00:07:03Z   User: JasonO 
*  Updating to current code. 
*  
*  Revision: 9   Date: 2009-01-05 16:55:54Z   User: JasonO 
*  
*  Revision: 8   Date: 2008-12-30 18:06:05Z   User: JasonO 
*  
*  Revision: 7   Date: 2008-12-23 20:20:43Z   User: JasonO 
*  updating latest changes 
*  
*  Revision: 6   Date: 2008-12-18 22:57:18Z   User: JasonO 
*  
*  Revision: 5   Date: 2008-12-10 00:29:12Z   User: JasonO 
*  Updating static bll methods. 
*  
*  Revision: 4   Date: 2008-12-02 00:14:38Z   User: nicka 
*  
*  Revision: 3   Date: 2008-12-02 00:10:49Z   User: JasonO 
*  
*  Revision: 2   Date: 2008-11-20 23:13:05Z   User: JasonO 
*  Creating facade to abastract logic of business objects from UI. 
*  
*  Revision: 1   Date: 2008-11-12 21:53:34Z   User: nicka 
**********************************************************************/
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text.RegularExpressions;

using Arena.Computer;
using Arena.Core;
using Arena.DataLayer.Core;
using Arena.Organization;

using Arena.Custom.Cccev.CheckIn.Entity;
using Arena.Custom.Cccev.DataUtils;

namespace Arena.Custom.Cccev.CheckIn
{
	/// <summary>
	/// CheckIn constants
	/// </summary>
	[Serializable]
	public class Constants
	{
		public const string SESS_STATE = "CCCEV_CHECKIN_STATE";
		public const string SESS_KEY_PEOPLEMAP = "cccev_peopleMap";
        public const string SESS_FAMILY = "cccev_checkInFamily";
		public const string SESS_LIST_CHECKIN_FAMILYMEMBERS = "cccev_checkinFamilyMemberList";
        public const string SESS_ATTENDEES = "cccev_checkinAttendees";
		public const string SESS_LIST_OCCURRENCES_CHECKIN = "cccev_checkinOccurrences";
        public const string SESS_SERVICE_TIMES = "serviceTimes";
        public const string SESS_RESULTS = "checkinResults";
		public static readonly DateTime NULL_DATE = DateTime.Parse( "1/1/1900" );
	}

    public enum CheckInStates
    {
        Init,
        FamilySearch,
        SelectFamilyMember,
        NoEligiblePeople,
        SelectAbility,
        SelectService,
        Confirm,
        Result,
        BadKiosk
    }

    public enum SearchTypes
    { 
        Scanner,
        PhoneNumber
    }

    [Serializable]
    public class Controller
    {
        /// <summary>
        /// Determines whether or not a family member is allowed to check in based on age and grade passed in.
        /// </summary>
        /// <param name="fm"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to check in</param>
        /// <param name="maxAge">Maximum age</param>
        /// <param name="maxGrade">Maximum grade level</param>
        /// <returns>bool indicating whether or not Family Member can check in to Occurrence Attendance</returns>
        public static bool CanCheckIn(FamilyMember fm, int maxAge, int maxGrade)
        {
            int gradeLevel = Person.CalculateGradeLevel(fm.GraduationDate, ArenaContext.Current.Organization.GradePromotionDate);
            bool gradDateKnown = fm.GraduationDate != Constants.NULL_DATE;
            bool gradeOK = gradeLevel <= maxGrade;
            bool ageOK = fm.Age <= maxAge;

            if (gradDateKnown)
            {
                return gradeOK;
            }

            if (fm.Age != -1)
            {
                if (maxAge != -1)
                {
                    return ageOK;
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Determines whether or not any user has the ability to check in to the provided Occurrence.
        /// </summary>
        /// <param name="occurrence"><see cref="Arena.Core.Occurrence">Occurrence</see> to check into</param>
        /// <returns>bool indicating whether or not Occurrence's checkin start time has passed</returns>
        public static bool ReadyForCheckIn(Occurrence occurrence)
        {
            return (occurrence.CheckInStart <= DateTime.Now);
        }

        /// <summary>
        /// Gets a family based on the specified search type and value provided.
        /// </summary>
        /// <param name="searchType">Enum that determines the type of search being done (by barcode, or by phone number)</param>
        /// <param name="value">Family ID or phone number</param>
        /// <returns>Collection of families that match the criteria provided</returns>
        public static FamilyCollection GetFamily(SearchTypes searchType, string value)
        {
            FamilyCollection families;

            switch (searchType)
            {
                case SearchTypes.Scanner:
                    families = GetFamiliesByAltID(value);
                    break;
                case SearchTypes.PhoneNumber:
                    families = GetFamiliesByPhoneNumber(value);
                    break;
                default:
                    families = new FamilyCollection();
                    break;
            }

            return families;
        }

        private static FamilyCollection GetFamiliesByAltID(string altID)
        {
            FamilyCollection families = new FamilyCollection();
            Person person = new Person();
            person.LoadByAlternateID(altID);
            families.Add(person.Family());
            return families;
        }

        private static FamilyCollection GetFamiliesByPhoneNumber(string phone)
        {
            FamilyCollection families = new FamilyCollection();
            FamilyCollection activeFamilies = new FamilyCollection();
            families.LoadByPhoneNumber(phone);

            foreach (Family family in families)
            {
                if (family.FamilyMembersActive.Count > 0)
                {
                    activeFamilies.Add(family);
                }
            }

            return activeFamilies;
        }

        /// <summary>
        /// Overload to only pass family.
        /// </summary>
        /// <param name="family"><see cref="Arena.Core.Family">Family</see> to find relatives for</param>
        /// <returns><see cref="Arena.Core.FamilyMemberCollection">FamilyMemberCollection</see> of current family and any relatives</returns>
        public static FamilyMemberCollection GetRelatives(Family family)
        {
            return GetRelatives(family, new int[0]);
        }

        /// <summary>
        /// Creates a FamilyMemberCollection of provided family's members and any relationships whose type matches one of the array members.
        /// </summary>
        /// <param name="family"><see cref="Arena.Core.Family">Family</see> to find relatives for</param>
        /// <param name="relationshipTypeIDs">int array of Relationship ID's</param>
        /// <returns><see cref="Arena.Core.FamilyMemberCollection">FamilyMemberCollection</see> of current family and any relatives</returns>
        public static FamilyMemberCollection GetRelatives(Family family, int[] relationshipTypeIDs)
        {
            FamilyMemberCollection familyMembers = family.FamilyMembersActive;
            
            if (relationshipTypeIDs.Length > 0)
            {
                FamilyMember fm = family.FamilyHead;

                foreach (Relationship rel in fm.Relationships)
                {
                    foreach (int i in relationshipTypeIDs)
                    {
                        if (rel.RelationshipTypeId == i && familyMembers.FindByID(rel.RelatedPersonId) == null)
                        {
                            FamilyMember relative = new FamilyMember(rel.RelatedPersonId);

                            if (relative.RecordStatus != Enums.RecordStatus.Inactive)
                            {
                                familyMembers.Add(relative);
                            }
                        }
                    }
                }
            }

            return familyMembers;
        }

        /// <summary>
        /// Loads an instance of the current kiosk based on the IP address.  If not found, will return null.
        /// </summary>
        /// <param name="ip">IP address of the kiosk</param>
        /// <returns><see cref="Arena.Computer.ComputerSystem">ComputerSystem</see> kiosk</returns>
        public static ComputerSystem GetCurrentKiosk(string ip)
        {
            string hostValue;
            ComputerSystem computer;

            try
            {
                hostValue = System.Net.Dns.GetHostEntry(ip).HostName;
            }
            catch (System.Net.Sockets.SocketException)
            {
                hostValue = System.Net.Dns.GetHostByAddress(ip).HostName;
            }

            if (Regex.IsMatch(hostValue, @"\d+\.\d+\.\d+\.\d+"))
            {
                computer = new ComputerSystem();
                computer.LoadByKioskIp(hostValue);
            }
            else
            {
                computer = new ComputerSystem(hostValue, true);
            }

            if (computer.SystemId != -1 && computer.Kiosk)
            {
                return computer;
            }

            return null;
        }
        
        /// <summary>
        /// Loads the active occurrences based on the location of the kiosk passed in and will create
        ///  a filtered OccurrenceCollection based on start and end dates.
        /// </summary>
        /// <param name="lookAhead">DateTime to start filter</param>
        /// <param name="currentTime">DateTime to end filter</param>
        /// <param name="kiosk"><see cref="Arena.Computer.ComputerSystem">ComputerSystem</see> kiosk</param>
        /// <returns>Filtered IEnumerable</returns>
        public static IEnumerable<Occurrence> GetOccurrences(DateTime lookAhead, DateTime currentTime, ComputerSystem kiosk)
        {
            OccurrenceCollection oc = new OccurrenceCollection();
            oc.LoadOccurrencesBySystemIDAndDateRange(kiosk.SystemId, currentTime, lookAhead);

            return (from o in oc
                    select o).Distinct();
        }

        /// <summary>
        /// Loads an OccurrenceAttendance based on Person ID and Occurrence Start Time.  Calls new extension method
        /// on OccurrenceAttendance (not part of the Arena Framework).
        /// </summary>
        /// <param name="startDate">Start Time of an occurrence</param>
        /// <param name="personID">Person ID</param>
        /// <returns>An <see cref="Arena.Core.OccurrenceAttendance">OccurrenceAttendance</see> loaded by start date and person id</returns>
        public static OccurrenceAttendance GetAttendance(DateTime startDate, int personID)
        {
            OccurrenceAttendance oa = new OccurrenceAttendance();
            return oa.LoadOccurrenceAttendanceByStartDateAndPersonID(startDate, personID);
        }

        /// <summary>
        /// Sets provided person attribute's value to max ability level lookup id.
        /// </summary>
        /// <param name="personAttribute">Value that represents the LookupID for max ability level.</param>
        /// <param name="maxAbilityLevelLookupValue">Int value for max ability lookup</param>
        public static void SetChildToMaxAbility(PersonAttribute personAttribute, int maxAbilityLevelLookupValue)
        {
            if (personAttribute != null)
            {
                personAttribute.IntValue = maxAbilityLevelLookupValue;
                personAttribute.Save(ArenaContext.Current.Organization.OrganizationID, ArenaContext.Current.User.Identity.Name);
            }
        }

        /// <summary>
        /// Creates an OccurrenceCollection based on the criteria passed in.
        /// </summary>
        /// <param name="occurrenceEvents">IEnumerable collection of Occurrences to filter</param>
        /// <param name="person"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to filter occurrences for</param>
        /// <param name="serviceTimes">Generic list of service times to filter by</param>
        /// <param name="abilityAttributeID">ID representing an Ability Level PersonAttribute</param>
        /// <param name="specialNeedsAttributeID">ID representing a Special Needs PersonAttribute</param>
        /// <returns>Filtered <see cref="Arena.Core.OccurrenceCollection">OccurrenceCollection</see></returns>
        public static IEnumerable<Occurrence> FilterOccurrences(IEnumerable<Occurrence> occurrenceEvents, FamilyMember person,
            List<DateTime> serviceTimes, int abilityAttributeID, int specialNeedsAttributeID)
        {
            Dictionary<DateTime, Occurrence> filteredOccurrences = new Dictionary<DateTime, Occurrence>();

            // Filtering occurrence collection by start time
            var classes = (from o in occurrenceEvents
                           let loc = new Location(o.LocationID)
                           where (serviceTimes.Contains(o.StartTime) && !loc.RoomClosed)
                           select o).Distinct();

            /// loop through each class occurrence and constrain choices by
            /// age/grade, special needs, ability level, etc
            foreach (Occurrence occurrence in classes)
            {
                bool matchesCriteria = false;

                // Check to see if filteredOccurrences already has an object w/ the same
                // start time. If it does, then no need to add another one with the same key.
                if (!filteredOccurrences.ContainsKey(occurrence.StartTime))
                {
                    // Now check existing Arena criteria
                    if (RequiredAgeAndGrade(person, occurrence.OccurrenceType) &&
                        RequiredGender(person, occurrence.OccurrenceType))
                    {
                        // Filter by custom criteria
                        OccurrenceTypeAttributeCollection occurrenceTypeAttributes = new OccurrenceTypeAttributeCollection(occurrence.OccurrenceTypeID);
                        OccurrenceTypeAttribute ota = occurrenceTypeAttributes[0];

                        // Fix 1/19 NA: If there is no OccurrenceTypeAttribute just skip these checks.
                        if (ota != null && ota.OccurrenceTypeAttributeId != -1)
                        {
                            if (RequiredSpecialNeeds(person, ota, specialNeedsAttributeID) &&
                                RequiredAbilityLevel(person, ota, abilityAttributeID) &&
                                RequiredLastName(person, ota))
                            {
                                matchesCriteria = true;
                            }
                        }
                        else
                        {
                            matchesCriteria = true;
                        }
                    }

                    if (matchesCriteria)
                    {
                        // Checking for tag syncing/membership
                        if (occurrence.OccurrenceType.MembershipRequired)
                        {
                            // if child is member of a synced profile, add occurrence
                            if (occurrence.OccurrenceType.SyncWithProfile != -1)
                            {
                                ProfileMember pm = new ProfileMember(occurrence.OccurrenceType.SyncWithProfile, person.PersonID);

                                if (pm.ProfileID != -1)
                                {
                                    filteredOccurrences.Add(occurrence.StartTime, occurrence);
                                }
                            }
                        }
                        else
                        {
                            filteredOccurrences.Add(occurrence.StartTime, occurrence);
                        }
                    }
                }
            }

            foreach (DateTime service in serviceTimes)
            {
                if (filteredOccurrences.ContainsKey(service))
                {
                    yield return filteredOccurrences[service];
                }
                else
                {
                    yield return GetEmptyOccurrence(service);
                }
            }
        }

        /// <summary>
        /// Iterates through Dictionary of family members, checks them in, and returns a Generic List of status strings.
        /// </summary>
        /// <param name="familyMap">Generic Dictionary of FamilyMembers (key) and OccurrenceCollections (value)</param>
		/// <param name="kiosk"><see cref="Arena.Computer.ComputerSystem">ComputerSystem</see> the family is standing at</param>
        /// <returns>Generic List of status messages</returns>
        public static IEnumerable<string> CheckInFamily(Dictionary<FamilyMember, IEnumerable<Occurrence>> familyMap, ComputerSystem kiosk)
        {
            foreach (KeyValuePair<FamilyMember, IEnumerable<Occurrence>> person in familyMap)
            {
                yield return CheckInFamilyMember(person.Key, person.Value, kiosk);
            }
        }

        /// <summary>
        /// Checks in an individual attendee.
        /// </summary>
        /// <param name="attendee"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to check in</param>
        /// <param name="occurrences"><see cref="Arena.Core.OccurrenceCollection">OccurrenceCollection</see> of Occurrences attendee can check into</param>
		/// <param name="kiosk"><see cref="Arena.Computer.ComputerSystem">ComputerSystem</see> the family is standing at</param>
        /// <returns>Status variable to be displayed on the view</returns>
        private static string CheckInFamilyMember(FamilyMember attendee, IEnumerable<Occurrence> occurrences, ComputerSystem kiosk)
        {
            bool success = true;
            string output = string.Empty;
            OccurrenceAttendance firstAttendance = null;

            foreach (Occurrence occurrence in occurrences)
            {
                try
                {
                    if ((occurrence is EmptyOccurrence ||
                        occurrence.OccurrenceID.Equals(-1)) &&
                        DateTime.Now > occurrence.CheckInEnd)
                    {
                        success = false;
                        output = string.Format("\t<tr><td class=\"resultTable\">{0}</td><td colspan=\"2\" class=\"resultTable fail\"><span class=\"fail\">{1}</span></td></tr>\n",
                            attendee.NickName, occurrence.Location);
                    }
                    else
                    {
                        OccurrenceAttendance attendance = new OccurrenceAttendance
                                                              {
                                                                  OccurrenceID = occurrence.OccurrenceID,
                                                                  PersonID = attendee.PersonID
                                                              };
                        ISecurityCode securityCode =
                            SecurityCodeHelper.GetSecurityCodeClass(SecurityCodeHelper.DefaultSecurityCodeSystem(ArenaContext.Current.Organization.OrganizationID));
                        attendance.SecurityCode = securityCode.GetSecurityCode();
                        attendance.Attended = true;
                        attendance.CheckInTime = DateTime.Now;
                        attendance.Save(ArenaContext.Current.User.Identity.Name);

                        if (firstAttendance == null)
                            firstAttendance = attendance;
                    }
                }
                catch (SqlException ex)
                {
                    success = false;
                    output = string.Format("\t<tr><td class=\"resultTable\">{0}</td><td class=\"resultTable fail\">{1}</td><td class=\"resultTable fail\"><span class=\"fail\">{2}</span></td></tr>\n",
                    attendee.NickName, occurrences.First().Location, "Check-In Failure");

                    try
                    {
                        // If SQL exception is generated, we want to log it w/o taking the user out of the checkin app
                        new ExceptionHistoryData().AddUpdate_Exception(ex, ArenaContext.Current.Organization.OrganizationID,
                                ArenaContext.Current.User.Identity.Name, ArenaContext.Current.ServerUrl);
                    }
                    catch { }
                }
            }

            if (success)
                output = PrintLabel(attendee, occurrences, firstAttendance, kiosk);

            return output;
        }

        /// <summary>
        /// Public facade to allow for manually printing labels.  Calls private PrintLabel() method.
        /// </summary>
        /// <param name="attendee"><see cref="Arena.Core.FamilyMember">FamilyMember</see> attending occurrence</param>
        /// <param name="occurrences"><see cref="Arena.Core.OccurrenceCollection">OccurrenceCollection</see> of Occurrences the attendee can check into</param>
		/// <param name="attendance"><see cref="Arena.Core.OccurrenceAttendance">OccurrenceAttendance</see> to be updated with failure status if print job fails</param>
		/// <param name="kiosk"><see cref="Arena.Computer.ComputerSystem">ComputerSystem</see> the family is standing at</param>
        /// <returns>boolean indicating whether or not the print job succeeded</returns>
        public static bool Print(FamilyMember attendee, IEnumerable<Occurrence> occurrences, OccurrenceAttendance attendance, ComputerSystem kiosk)
        {
            if (PrintLabel(attendee, occurrences, attendance, kiosk).Contains("Print Failure"))
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Tells printer to print CheckIn label.
        /// </summary>
        /// <param name="attendee"><see cref="Arena.Core.FamilyMember">FamilyMember</see> attending occurrence</param>
        /// <param name="occurrences"><see cref="Arena.Core.OccurrenceCollection">OccurrenceCollection</see> of Occurrences the attendee can check into</param>
        /// <param name="attendance"><see cref="Arena.Core.OccurrenceAttendance">OccurrenceAttendance</see> to be updated with failure status if print job fails</param>
		/// <param name="kiosk"><see cref="Arena.Computer.ComputerSystem">ComputerSystem</see> the family is standing at</param>
        /// <returns>Status variable to be displayed on the view</returns>
        private static string PrintLabel(FamilyMember attendee, IEnumerable<Occurrence> occurrences, OccurrenceAttendance attendance, ComputerSystem kiosk)
        {
			try
            {
                IPrintLabel pl = PrintLabelHelper.GetPrintLabelClass(PrintLabelHelper.DefaultPrintLabelSystem(ArenaContext.Current.Organization.OrganizationID));
                pl.Print(attendee, occurrences, attendance, kiosk);
                return string.Format("\t<tr><td class=\"resultTable\">{0}</td><td class=\"resultTable\"><span class=\"classroomText\">{1}</span></td><td class=\"resultTable success\">&nbsp;</td></tr>\n",
                    attendee.NickName, occurrences.First().Location);
            }
            catch (Exception ex)
            {
                attendance.Notes = "Print Failure";
                attendance.Save(ArenaContext.Current.User.Identity.Name);

                try
                {
                    new ExceptionHistoryData().AddUpdate_Exception(ex, ArenaContext.Current.Organization.OrganizationID,
                                ArenaContext.Current.User.Identity.Name, ArenaContext.Current.ServerUrl);
                }
                catch { }

                return string.Format("\t<tr><td class=\"resultTable\">{0}</td><td class=\"resultTable\">{1}</td><td class=\"resultTable fail\"><span class=\"fail\">{2}</span></td></tr>\n",
                    attendee.NickName, occurrences.First().Location, attendance.Notes);
            }
        }

        private static Occurrence GetEmptyOccurrence(DateTime startTime)
        {
            return new EmptyOccurrence(startTime);
        }

        /// <summary>
        /// Shortens and adds an elipsis ("...") to provides string to 36 characters.
        /// </summary>
        /// <param name="text">string to shorten</param>
        /// <returns>Truncated string</returns>
        public static string TruncateText(string text)
        {
            if (text.Length >= 20)
            {
                return text.Substring(0, 19) + "...";
            }

            return text;
        }

        /// <summary>
        /// Determines whether or not age or grade are required for checkin.
        /// </summary>
        /// <param name="person"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to test against</param>
        /// <param name="type"><see cref="Arena.Core.OccurrenceType">OccurrenceType</see> that determines test criteria</param>
        /// <returns>bool based on whether the person's age or grade falls within the allowable ages or grades</returns>
        private static bool RequiredAgeAndGrade(Person person, OccurrenceType type)
        {
            if (type.MinGrade != -1 || type.MaxGrade != -1)
            {
                int gradeLevel = Person.CalculateGradeLevel(person.GraduationDate, ArenaContext.Current.Organization.GradePromotionDate);
                return (gradeLevel >= type.MinGrade && gradeLevel <= type.MaxGrade);
            }
            
            if ((type.MinAge != 0 || type.MaxAge != 0) &&
                (type.MinAge != -1 || type.MaxAge != -1))
            {
                decimal fractionalAge = DateUtils.GetFractionalAge(person.BirthDate);
                return (fractionalAge >= type.MinAge && fractionalAge <= type.MaxAge);
            }
            
            return true;
        }

        /// <summary>
        /// Determines whether or not special needs is required for checkin.
        /// </summary>
        /// <param name="person"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to test against</param>
        /// <param name="attribute"><see cref="Arena.Custom.Cccev.CheckIn.Entity.OccurrenceTypeAttribute">OccurrenceTypeAttribute</see> that determines test criteria</param>
        /// <param name="attributeID">ID representing a Special Needs PersonAttribute</param>
        /// <returns>bool based on whether the person attribute matches the OccurrenceTypeAttribute's requirement for special needs</returns>
        private static bool RequiredSpecialNeeds(Person person, OccurrenceTypeAttribute attribute, int attributeID)
        {
            if (attribute == null)
            {
                return true;
            }
            
            if (!attribute.IsSpecialNeeds)
            {
                return true;
            }
            
            PersonAttribute pa = new PersonAttribute(person.PersonID, attributeID);
            bool specialNeeds = pa.IntValue == 1;  // Arena Framework uses int values in Person Attribute to reflect true/false
            return (specialNeeds == attribute.IsSpecialNeeds);
        }

        /// <summary>
        /// Determines whether or not certain ability levels are required for checkin.
        /// </summary>
        /// <param name="person"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to test against</param>
        /// <param name="attribute"><see cref="Arena.Custom.Cccev.CheckIn.Entity.OccurrenceTypeAttribute">OccurrenceTypeAttribute</see> that determines test criteria</param>
        /// <param name="attributeID">ID representing an Ability Level PersonAttribute</param>
        /// <returns>bool based on whether the person attribute matches the OccurrenceTypeAttribute's requirement for ability level</returns>
        private static bool RequiredAbilityLevel(Person person, OccurrenceTypeAttribute attribute, int attributeID)
        {
            bool result = false;

            if (attribute == null)
            {
                result = true;
            }
            else if (attribute.AbilityLevelLookupIDs.Count == 0 )
            {
                result = true;
            }
            else
            {
                PersonAttribute pa = new PersonAttribute(person.PersonID, attributeID);
				foreach ( int abilityLevel in attribute.AbilityLevelLookupIDs )
				{
					if ( pa.IntValue == abilityLevel )
					{
						result = true;
						break;
					}
				}
            }

            return result;
        }

        /// <summary>
        /// Determines whether or not gender is required for checkin.
        /// </summary>
        /// <param name="person"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to test against</param>
        /// <param name="type"><see cref="Arena.Core.OccurrenceType">OccurrenceType</see> that determines test criteria</param>
        /// <returns>bool based on whether the person's gender matches the gender requirement of the occurrence type</returns>
        private static bool RequiredGender(Person person, OccurrenceType type)
        {
            if (type.GenderPreference == GenderPreference.Everyone)
            {
                return true;
            }
            
            return (person.Gender.ToString() == type.GenderPreference.ToString());
        }

        /// <summary>
        /// Determines whether or not last initial needs to fall within a given range for checkin.
        /// </summary>
        /// <param name="person"><see cref="Arena.Core.FamilyMember">FamilyMember</see> to test against</param>
        /// <param name="attribute"><see cref="OccurrenceTypeAttribute">OccurrenceTypeAttribute</see> that determines test criteria</param>
        /// <returns>bool based on whether the person's last initial falls within the range required by the occurrence type attribute</returns>
        private static bool RequiredLastName(Person person, OccurrenceTypeAttribute attribute)
        {
            if (attribute == null)
            {
                return true;
            }
            
            if (attribute.LastNameStartingLetter.Trim() == string.Empty && 
                attribute.LastNameEndingLetter.Trim() == string.Empty)
            {
                return true;
            }
            
            char rangeStart = 'A';
            char rangeEnd = 'Z';

            if (attribute.LastNameStartingLetter.Trim() != string.Empty)
                rangeStart = char.Parse(attribute.LastNameStartingLetter.ToUpper());

            if (attribute.LastNameEndingLetter.Trim() != string.Empty)
                rangeEnd = char.Parse(attribute.LastNameEndingLetter.ToUpper());

            char lastInitial = char.Parse(person.LastName.Substring(0, 1).ToUpper());
            return (lastInitial >= rangeStart && lastInitial <= rangeEnd);
        }
    }
}
